package org.ooda.distributedsys.functionmodule.function;

import com.mathworks.toolbox.javabuilder.MWArray;
import com.mathworks.toolbox.javabuilder.MWClassID;
import com.mathworks.toolbox.javabuilder.MWException;
import com.mathworks.toolbox.javabuilder.MWNumericArray;

import eventtrigger_1.EventTrigger;
import org.ooda.distributedsys.functionmodule.common.ResponseHandler;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;

/**
 * @program: distributedSys
 * @description: 控制协议matlab算法控制类
 * @author: Aprilies
 * @create: 2023-05-15 16:47
 **/

@RestController
@RequestMapping("controlprotocol")
public class ControlProtocol {

    private final Queue<Runnable> requestQueue = new LinkedList<>();

    private boolean isProcessingRequest = false;

    public static final EventTrigger eventTrigger;

    static {
        try {
            eventTrigger = new EventTrigger();
        } catch (MWException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * check eventTrigger params is validate, the rule is:
     * 1,x_1.length == 6
     * 2,x_2.length == 9
     * 3,T >= 0 && T <= 50
     * 4,dt >= 0.001 && dt <= 5
     * 5,T >= dt
     * @param T
     * @param dt
     * @return true if validate
     */
    private boolean checkParamsIsValidate(int T, double dt){
        if(T < 0 || T > 50 || dt < 0.001 || dt > 5 || T < dt)
            return false;
        return true;
    }

    private boolean checkXIsValidata(double[] x, int n){
        if(x.length != n)
            return false;
        return true;
    }

    private boolean checkSatisfyEquation(double rho, double delta, double beta){
        if(rho < 0 || beta < 0 || beta > 1 || delta < (1-beta)*rho)
            return false;
        return true;
    }

    /**
     * eventTrigger matlab function
     * @param x_1 6 dimension vector of some agent
     * @param x_2 9 dimension vector of some agent
     * @param T system duration
     * @param dt system step num
     * @return ResponseEntity
     */

    @PostMapping("/eventtrigger")
    public DeferredResult<ResponseEntity<?>> eventTrigger(@RequestParam(value="x_1",defaultValue="0,0,0,0,0,0") double[] x_1,
                                                           @RequestParam(value="x_2",defaultValue="0,0,0,0,0,0") double[] x_2,
                                                           @RequestParam(value="x_3",defaultValue="0,0,0,0,0,0") double[] x_3,
                                                           @RequestParam(value="x_4",defaultValue="0,0,0,0,0,0") double[] x_4,
                                                           @RequestParam(value="x_5",defaultValue="0,0,0,0,0,0,0,0,0") double[] x_5,
                                                           @RequestParam(value="x_6",defaultValue="0,0,0,0,0,0,0,0,0") double[] x_6,
                                                           @RequestParam(value="rho",defaultValue="0.5") double rho,
                                                           @RequestParam(value="delta0",defaultValue="0.2") double delta0,
                                                           @RequestParam(value="beta0",defaultValue="0.6") double beta0,
                                                           @RequestParam(value="delta",defaultValue="0.5") double delta,
                                                           @RequestParam(value="beta",defaultValue="0.5") double beta,
                                                           @RequestParam(value="k",defaultValue="0,1,2,4") double[] Ik,
                                                           @RequestParam(value="Iomega",defaultValue="0,0,0,0,0,0") double[] Iomega,
                                                           @RequestParam(value="T",defaultValue="20") int T,
                                                           @RequestParam(value="dt",defaultValue="0.01") double dt,
                                                          @RequestParam(value="N", defaultValue = "21") int N){
        if(!(checkParamsIsValidate(T,dt) || checkXIsValidata(x_1,6) || checkXIsValidata(x_2,9) || checkXIsValidata(x_3,6) || checkXIsValidata(x_4,6) || checkXIsValidata(x_5,9) || checkXIsValidata(x_6,9) || checkSatisfyEquation(rho,delta,beta) || checkSatisfyEquation(rho,delta0,beta0) || checkXIsValidata(Iomega,6) || checkXIsValidata(Ik,4))){
            DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>();
            deferredResult.setResult(ResponseHandler.generateResponse("params is not validate",HttpStatus.BAD_REQUEST, null));
            return deferredResult;
        }

        DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>();

        Runnable requestTask = () -> {
            Object[] objects = new Object[0];
            try {
                MWNumericArray MW_x_1 = new MWNumericArray(x_1, MWClassID.DOUBLE);
                MWNumericArray MW_x_2 = new MWNumericArray(x_2, MWClassID.DOUBLE);
                MWNumericArray MW_x_3 = new MWNumericArray(x_3, MWClassID.DOUBLE);
                MWNumericArray MW_x_4 = new MWNumericArray(x_4, MWClassID.DOUBLE);
                MWNumericArray MW_x_5 = new MWNumericArray(x_5, MWClassID.DOUBLE);
                MWNumericArray MW_x_6 = new MWNumericArray(x_6, MWClassID.DOUBLE);
                MWNumericArray MW_rho = new MWNumericArray(rho, MWClassID.DOUBLE);
                MWNumericArray MW_delta0 = new MWNumericArray(delta0, MWClassID.DOUBLE);
                MWNumericArray MW_beta0 = new MWNumericArray(beta0, MWClassID.DOUBLE);
                MWNumericArray MW_delta = new MWNumericArray(delta, MWClassID.DOUBLE);
                MWNumericArray MW_beta = new MWNumericArray(beta, MWClassID.DOUBLE);
                MWNumericArray MW_Ik = new MWNumericArray(Ik, MWClassID.DOUBLE);
                MWNumericArray MW_Iomega = new MWNumericArray(Iomega, MWClassID.DOUBLE);
                MWNumericArray MW_T = new MWNumericArray(T, MWClassID.DOUBLE);
                MWNumericArray MW_dt = new MWNumericArray(dt, MWClassID.DOUBLE);
//                EventTrigger eventTrigger = new EventTrigger();
                System.out.println("start to run control protocol=============================");
                objects = eventTrigger.eventtrigger_1(5,MW_x_1,MW_x_2,MW_x_3,MW_x_4,MW_x_5,MW_x_6,MW_rho,MW_delta0,MW_beta0,MW_delta,MW_beta,MW_Iomega,MW_Ik,MW_T,MW_dt);
                System.out.println("end to run control protocol=============================");
//                eventTrigger.dispose();
                MWArray.disposeArray(MW_x_1);
                MWArray.disposeArray(MW_x_2);
                MWArray.disposeArray(MW_x_3);
                MWArray.disposeArray(MW_x_4);
                MWArray.disposeArray(MW_x_5);
                MWArray.disposeArray(MW_x_6);
                MWArray.disposeArray(MW_rho);
                MWArray.disposeArray(MW_delta0);
                MWArray.disposeArray(MW_beta0);
                MWArray.disposeArray(MW_delta);
                MWArray.disposeArray(MW_beta);
                MWArray.disposeArray(MW_Iomega);
                MWArray.disposeArray(MW_Ik);
                MWArray.disposeArray(MW_T);
                MWArray.disposeArray(MW_dt);
//                eventTrigger.dispose();
                System.gc();
            }
            catch (Exception e){
                e.printStackTrace();
                ResponseEntity<?> response = ResponseHandler.generateResponse("function runtime error", HttpStatus.INTERNAL_SERVER_ERROR, null);
                deferredResult.setResult(response);
            }
            // 处理y_show数据
            HashMap<String, Object> y_show = new HashMap<>();
            double[][][] o = (double[][][])((MWNumericArray) objects[0]).toDoubleArray();
            for (int i = 0; i < N; i++){
                ArrayList<Object> objects2 = new ArrayList<>();
                for (int j = 0; j < o[0].length; j++){
                    ArrayList<Double> doubles = new ArrayList<>();
                    for (int k = 0; k < 3; k++){
                        doubles.add(o[k][j][i]);
                    }
                    objects2.add(doubles);
                }
                y_show.put("threed" + i, objects2);
            }
            // 处理instants数据
            double[] triggers = ((MWNumericArray) objects[1]).getDoubleData();
            double[][] instantsDoubleArray = (double[][]) ((MWNumericArray) objects[2]).toDoubleArray();
            HashMap<String, Object> instants = new HashMap<>();
            for (int i = 0; i < N; i++){
                ArrayList<Object> objects1 = new ArrayList<>();
                for (int j = 0; j < (int)triggers[i]; j++){
                    ArrayList<Double> doubles = new ArrayList<>();
                    doubles.add(instantsDoubleArray[i][j]);
                    doubles.add((double)i);
                    objects1.add(doubles);
                }
                instants.put("com" + i, objects1);
            }
            String s = objects[3].toString();
            // 处理e_show数据
            HashMap<String, Object> e_show = new HashMap<>();
            double[][][] e = (double[][][])((MWNumericArray) objects[4]).toDoubleArray();
            assert e[0] != null : "e[0] is null";
            // 三维
            for (int i = 0; i < 3; i++){
                // 五个智能体，除去领导智能体
                ArrayList<Object> objects1 = new ArrayList<>();
                for ( int j = 1; j < N; j++){
                    ArrayList<Object> objects2 = new ArrayList<>();
                    for ( int k = 0; k < e[0].length; k++){
                        objects2.add(e[i][k][j]);
                    }
                    objects1.add(objects2);
                }
                e_show.put("dimension_" + i, objects1);
            }

            HashMap<String, Object> ret = new HashMap<>();
            ret.put("y_show", y_show);
            ret.put("instants", instants);
            ret.put("png_id",s);
            ret.put("e_show",e_show);
            ResponseEntity<?> response = ResponseHandler.generateResponse("success", HttpStatus.OK, ret);
            deferredResult.setResult(response);
            // Send the response
            synchronized (this) {
                isProcessingRequest = false;
                if (!requestQueue.isEmpty()) {
                    // If there are pending requests, process the next one
                    Runnable nextRequest = requestQueue.poll();
                    nextRequest.run();
                }
            }
            System.gc();
        };

        if (isProcessingRequest) {
            // If another request is already being processed, enqueue the current request
            requestQueue.offer(requestTask);

        } else {
            // Process the current request
            isProcessingRequest = true;
            requestTask.run();
        }
        return deferredResult;
    }

    @GetMapping(value="/png")
    @ResponseBody
    public ResponseEntity<?> getPNG(@RequestParam(value = "png_id") String png_id) {
        HttpHeaders headers = new HttpHeaders();
        File file = new File("/tmp/" + png_id + ".png");
        System.out.println(file.getAbsolutePath());
        try {
            if (file.exists()){
                //设置Content-type为image/jpeg
                headers.add(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE);
                FileInputStream inputStream = new FileInputStream(file);
                byte[] bytes = new byte[inputStream.available()];
                inputStream.read(bytes, 0, inputStream.available());
                ResponseEntity<Object> response = new ResponseEntity<Object>(bytes, headers, HttpStatus.OK);
                // close inputStream
                inputStream.close();
                // delete png file
                boolean delete = file.delete();
                return response;
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return ResponseHandler.generateResponse("cannot find png, please try again", HttpStatus.BAD_REQUEST, null);
    }

}
